Repository navigation
Give masks a plane identity (Z/T); refuse 2D masks of multi-plane images - #43
Merged
Merged
Conversation
A mask was accepted when it was 2D with the image's Y and X sizes, with no plane identity: one 2D mask paired with a time-lapse or Z-stack would be applied to every plane, guessing object identity across T and Z, while per-plane masks (TYX/ZYX) were refused. - find_masks reads each mask's axes from its own metadata. Besides Y and X a mask may have Z and/or T; any other or unnamed axis (a plain stack that could be depth or time) stays mask_not_2d, never guessed. The masks of one image must share their planes (mask_planes_differ). - mask_plane_issues compares each mask's Z/T lengths with its image's under the confirmed axis roles; run_experiment stops on mask_planes_mismatch, so a 2D mask is refused for a Z-stack or time-lapse. - MaskFile.axes/planes; the manifest's mask entries gain axes and shape (optional keys, manifest version 1). - read_mask(z=, t=) reads one plane (one TIFF page when stored per page) and requires z/t for a mask with those axes; scan_masks scans plane by plane. - Contract §8/§9/§15, user guide, plan Q32 and AGENTS.md updated. Fixes #36 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Scma6W3LC11s3qjXHZuGDm
Review of #43 (issue #36): the README's masks bullet now says a mask is 2D or has Z/T planes named in its own metadata, one per image plane; a 2D mask of a Z-stack or time-lapse is refused (mask_planes_mismatch), projections included; and the manifest records each mask's axes and shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbytvBRB9CmKQ5AAkhEFcv
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VbytvBRB9CmKQ5AAkhEFcv
Contributor
Author
|
Review follow-up, pushed as 642eebc and 033ac4a (docs only):
Generated by Claude Code |
…ntity # Conflicts: # docs/contract.md
Contributor
Author
|
Merged main (#42) in: contract §8 error/warning rows combined (#43's mask plane errors plus #42's Generated by Claude Code |
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #36.
find_masksreads each mask's axes from its own metadata: the tifffile series axes, or the OME-NGFF axes of a label image. New fieldMaskFile.axes;MaskFile.planesgives{"Z": n, "T": n}.mask_not_2d, with a new message and action. This includes a plain stack that tifffile callsQ/I, which could be either depth or time.mask_planes_differ: the masks of one image do not have the same planes.mask_plane_issues(inventory, images, file_mappings)(exported): errormask_planes_mismatchwhen a mask's Z/T lengths are not its image's under the confirmed axis roles. So a 2D mask is refused for a Z-stack or a time-lapse.read_mask(..., z=, t=)reads one plane. It requiresz/tfor a mask that has those axes and refuses an out-of-range plane. When planes are stored one per page it reads only that page; a volume stored in one page is read whole and the plane taken from it. OME-Zarr label images are sliced.to_manifest()addsaxesandshapeto each mask entry.run_experimentaddsmask_plane_issuesafter the axis review, somask_planes_mismatchblocks the run.scan_masksscans a mask with Z/T one plane at a time, each plane counted like an image of its own. Objects are never matched across planes.MaskTypeSummary.imagesstill counts images;objectscounts a label once per plane it appears in.masks.files[].axes,shape), §15 (new Planes row) and §15.1;Decisions / notes for review
mask_planes_mismatch). Allowing it later would need an explicit, recorded confirmation.QYX) ismask_not_2d. Letting the user confirm a mask's axes, as for the images, could be a later feature.axesandshape, following this repo's existing pattern. Older manifests only ever held 2D masks. But a consumer that ignoresaxesand opens a TYX mask from a new manifest would get a 3D array. Contract §9 now says consumers must readaxesand never apply a 2D mask to several planes. If you prefer a version bump here, note that the reader accepts only version"1": a bump would make every existing manifest unreadable and force a full rebuild.checkand themaskscommand have no confirmed roles, so they cannot compare masks with images. Onlyrunreportsmask_planes_mismatch.mask_planes_differis found from the headers everywhere.masks.py/mask_scan.py.Tests
New tests:
tests/test_masks.py:test_a_2d_mask_of_a_time_lapse_or_z_stack_is_refused(main regression test)test_a_mask_with_the_images_time_points_is_accepted_and_recorded(no pixel reads)test_the_run_records_each_masks_planestest_a_mask_with_other_planes_than_its_image_is_refusedtest_a_mask_whose_extra_axis_is_not_named_is_refusedtest_read_mask_reads_one_plane_and_never_guesses_whichtest_read_mask_of_a_volume_stored_in_one_pagetest_label_images_with_time_points(OME-Zarr,tyx)tests/test_mask_scan.py::test_masks_with_time_points_are_scanned_plane_by_plane: a TYX scan gives the same evidence as three single-plane images.test_the_run_records_the_masks_in_the_manifestnow expectsaxes/shape.Results:
test_discovery.py:472: running as root ignores file modes.ruff check .andruff format --check .are clean.Generated by Claude Code